﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using System.Collections;
using System.Data;

namespace mlnx_os_sysdump_analyzer
{
    enum SWProfile { IB, ETH, VPI, NA };
    enum SWFamily { SX, FIT, NA };

    class Messages : IDisposable
    {
        private bool KEEP_RUNNING;
        private string OUTPUT_DIR_PATH;

        // keywords for errors
        //private string[] errosKeywords = new string[] { ".ERR", "PLB", "crash", "go bit not cleared", "timed out", "Linux", "updt_role", "HERE" };
        private System.Collections.Specialized.StringCollection messagesKeywords = Properties.Settings.Default.messagesKeywords;
        //
        private string tmpFolder = "";
        private ArrayList threadsList = null;
        //
        private ArrayList errorsDataTables = null;
        private Object errorsDataTablesLock = new Object();
        // count how many times the switch was powered on.
        private long powerOnCounter = 0;
        private Object powerOnCounterLock = new Object();
        //
        private DataTable ledIndicationsTable = null;
        private Object ledIndicationsTableLock = new Object();
        //
        private DataTable daemonsDataTable = null;
        private Object daemonsDataTableLock = new Object();
        public string daemonsLoadVerification = "";
        public string daemonsStatus = "";

        // 
        private DateTime uBootVersoinDate = DateTime.MinValue;
        public string uBootVersoin = "";
        private Object uBootVersoinLock = new Object();
        //
        public DataTable logsPrintGaps = null;
        private Object logsPrintGapsLock = new Object();
        //

        //----------------------------------------------------
        
        /// <summary>
        /// constructor.
        /// </summary>
        /// <param name="KEEP_RUNNING"></param>
        /// <param name="OUTPUT_DIR_PATH"></param>
        public Messages(ref bool KEEP_RUNNING, ref string OUTPUT_DIR_PATH)
        {
            this.KEEP_RUNNING = KEEP_RUNNING;   
            this.OUTPUT_DIR_PATH = OUTPUT_DIR_PATH;
            
            daemonsDataTable = new DataTable();
            // add columns
            daemonsDataTable.Columns.Add("Time", typeof(DateTime));
            daemonsDataTable.Columns.Add("Message", typeof(string));

            //
            logsPrintGaps = new DataTable();
            logsPrintGaps.Columns.Add("Gap size [seconds]", typeof(int));
            logsPrintGaps.Columns.Add("File name", typeof(string));
            logsPrintGaps.Columns.Add("Time 1", typeof(DateTime));
            logsPrintGaps.Columns.Add("Message 1", typeof(string));
            logsPrintGaps.Columns.Add("Time 2", typeof(DateTime));
            logsPrintGaps.Columns.Add("Message 2", typeof(string));

            ledIndicationsTable = new DataTable();
            // add columns
            ledIndicationsTable.Columns.Add("Time", typeof(DateTime));
            ledIndicationsTable.Columns.Add("LED name", typeof(string));
            ledIndicationsTable.Columns.Add("Color", typeof(string));
        }
        //
        //----------------------------------------------------
        
        /// <summary>
        /// starts scannign all messages files. each file will be scanned in a seperate thread.
        /// </summary>
        public void scanForErrors()
        {
            tmpFolder = OUTPUT_DIR_PATH + "\\messages_tmp";
            errorsDataTables = new ArrayList();
            //
            threadsList = new ArrayList();
            startScanForErrors(OUTPUT_DIR_PATH);
            // wait for all threads
            for (int i = 0; i < threadsList.Count; i++)
            {
                ((Thread)threadsList[i]).Join();
            }
        }
        //
        //----------------------------------------------------

        /// <summary>
        /// recursively find messages files and scan them.
        /// </summary>
        /// <param name="outputFolder"></param>
        public void startScanForErrors(string outputFolder)
        {
            try
            {
                if (outputFolder == null)
                {
                    return;
                }
                DirectoryInfo di = new DirectoryInfo(outputFolder);
                if (di.Exists == false)
                {
                    return;
                }
                bool messagesFilesScanned = false;
                foreach (FileInfo file in di.GetFiles())
                {
                    if (file.Name.Contains("messages"))
                    {
                        messagesFilesScanned = true;
                        if (file.Name.Contains(".gz"))
                        {
                            if (Methods.ExtractGzFile(file.FullName, tmpFolder))
                            {
                                // scan in a new thread
                                Thread th = new Thread(new ParameterizedThreadStart(scanForErrorsInFile));
                                threadsList.Add(th);
                                string[] param = { tmpFolder + "\\" + file.Name.Substring(0, file.Name.Length - file.Extension.Length), file.Name };
                                th.Start(param);
                                // note: the file will be deleted later along with the other files.
                            }
                        }
                        else
                        {
                            Thread th = new Thread(new ParameterizedThreadStart(scanForErrorsInFile));
                            threadsList.Add(th);
                            string[] param = { file.FullName, file.Name };
                            th.Start(param);
                        }
                    }
                }
                // if we found the messages files, then stop the recursion.
                if (!messagesFilesScanned) 
                {
                    foreach (DirectoryInfo subfolder in di.GetDirectories())
                    {
                        startScanForErrors(subfolder.FullName);
                    }
                }
            }
            catch { }
        }
        //
        //----------------------------------------------------
        
        /// <summary>
        /// scan the file for errors according to the settings
        /// </summary>
        /// <param name="param"></param>
        public void scanForErrorsInFile(object param)
        {
            string srcFilePath = ((string[])param)[0];
            string fileName = ((string[])param)[1];
            if (srcFilePath == null || !File.Exists(srcFilePath))
                return;
            StreamReader sr = new StreamReader(srcFilePath);
            ErrorsFields ef = new ErrorsFields();
            ef.fileName = fileName;
            string tmpLed = "";
            ArrayList tmpList = new ArrayList();
            //
            DateTime previousTime = DateTime.MinValue;
            string previousMsg = "";
            int logsIdleThresholdSec = Properties.Settings.Default.logsIdleThresholdSec;
            //
            try
            {
                while (KEEP_RUNNING && sr.Peek() >= 0)
                {
                    // read line
                    string line = sr.ReadLine();
                    try
                    {
                        // get the time and the message 
                        string[] arr = Regex.Split(Methods.RemoveExtraSpaces(line), " ");
                        DateTime time = Methods.StringToDateTime(arr[0] + " " + arr[1] + " " + arr[2]);
                        string message = "";
                        for (int i = 3; i < arr.Length; i++)
                        {
                            message += arr[i] + " ";
                        }
                        // --

                        // check if there is no log prints for more then X seconds.
                        if (previousTime == DateTime.MinValue)
                        {
                            // initialzie for the first time
                            previousTime = time;
                            previousMsg = message;
                        }
                        else
                        {
                            // calculate the difference
                            int gapSize = Int32.Parse(time.Subtract(previousTime).TotalSeconds.ToString());
                            if ( gapSize > logsIdleThresholdSec)
                            {
                                lock (logsPrintGapsLock)
                                {
                                    logsPrintGaps.Rows.Add(gapSize, fileName, previousTime, previousMsg, time, message);
                                }
                            }
                        }
                        previousTime = time;
                        previousMsg = message;
                        // ---
                        
                        // add to the full table.
                        ef.fullContent.Rows.Add(time, message);

                        // get U-Boot version
                        if (line.Contains("U-Boot version"))
                        {
                            lock (uBootVersoinLock) // aquire lock
                            {
                                if (time > uBootVersoinDate)
                                {
                                    uBootVersoin = Regex.Split(message, "U-Boot version:")[1];
                                }
                            }
                        }

                        // count how many times the switch was powered on.
                        if (line.Contains("Linux version"))
                        {
                            lock (powerOnCounterLock) // aquire lock
                            {
                                powerOnCounter++;
                            }
                        }

                        // check led indication
                        if (line.Contains("led_name: "))
                        {
                            tmpLed = time + "_" + Regex.Split(line, "led_name: ")[1] + "_";
                        }
                        if (line.Contains("led_color: "))
                        {
                            tmpLed += mapNumToColor(Regex.Split(line, "led_color: ")[1]);
                            tmpList.Add(tmpLed);
                            tmpLed = "";
                        }
                        // --

                        // add to daemons list
                        if (line.Contains("pm.") || line.Contains("Linux version") || (line.Contains("launched") && line.Contains(".NOTICE") && !line.Contains("cli.")))
                        {
                            lock (daemonsDataTableLock)
                            {
                                daemonsDataTable.Rows.Add(time, message);
                            }
                        }
                        
                        //// ## check for errors
                        ////for (int i = 0; i < errosKeywords.Length; i++)
                        //for (int i = 0; i < messagesKeywords.Count; i++)
                        //{
                        //    if (line.Contains(messagesKeywords[i]))
                        //    {
                        //        ef.errorsOnly.Rows.Add(time, message);
                        //        break;
                        //    }
                        //}
                        //// check for err=<!=0>
                        //if (line.Contains("err="))
                        //{
                        //    String tmp = line.Substring(line.IndexOf("err=") + 4);
                        //    int i = tmp.IndexOf(" ");
                        //    if (i > 0)
                        //    {
                        //        tmp = tmp.Substring(0, i);
                        //    }
                        //    tmp = Regex.Replace(tmp, ",", "");
                        //    if (Int32.Parse(tmp) != 0) 
                        //    {
                        //        ef.errorsOnly.Rows.Add(time, message);
                        //    }
                        //}
                        // ## end of checking for errors
                    }
                    catch
                    { }
                }
            }
            catch 
            {
            }
           
            //if (ef.errorsOnly.Rows.Count == 0)
            //{
            //    ef.errorsOnly.Rows.Add(DateTime.Now, "******  No errors were found in this file.  ******");
            //}
            
            lock (ledIndicationsTableLock)
            {
                for (int i = 0; i < tmpList.Count; i++)
                {
                    string[] arr = Regex.Split((string)tmpList[i], "_");
                    ledIndicationsTable.Rows.Add(Methods.StringToDateTime(arr[0]), arr[1], arr[2]);
                }
            }

            lock (errorsDataTablesLock)
            {
                errorsDataTables.Add(ef);
            }
            sr.Close();
            sr.Dispose();

            try
            {
                /// sort
                if (daemonsDataTable.Rows.Count > 0)
                {
                    /// convert DataTable to DataView  
                    DataView dv = daemonsDataTable.DefaultView;
                    /// apply the sort on Time column in descending order
                    dv.Sort = "Time DESC";
                    /// save our newly ordered results back into our datatable  
                    daemonsDataTable = dv.ToTable();
                }
            }
            catch { }
        }
        //
        //----------------------------------------------------
        
        /// <summary>
        /// gets the errors table
        /// </summary>
        /// <returns></returns>
        public ArrayList getErrors()
        {
        	// sort the list
        	Methods.sortErrorsFieldsArrayList(ref errorsDataTables);
            //
            return errorsDataTables;
        }
        //
        //----------------------------------------------------
        
        /// <summary>
        /// how many times the switches was restarted
        /// </summary>
        /// <returns></returns>
        public string getPowerOnCounter()
        {
            return "The switch was restarted: " + powerOnCounter + " times (According to the available messages files).";
        }
        //
        //----------------------------------------------------
       
        /// <summary>
        /// convert color code to color (for SX leds).
        /// </summary>
        /// <param name="color">color code</param>
        /// <returns></returns>
        private string mapNumToColor(string color)
        {
            switch (color)
            {
                case "0":
                    return "NOCOLOR";
                case "1":
                    return "YELLOW";
                case "2":
                    return "GREEN";
                case "3":
                    return "Red";
                case "4":
                    return "Yellow_blink";
                case "5":
                    return "Green_blink";
                default:
                    return "NA";
            }
        }
        //
        //----------------------------------------------------
       
        /// <summary>
        /// get the table with the leds indication
        /// </summary>
        /// <returns></returns>
        public DataTable getLedIndications()
        {
            return ledIndicationsTable;
        }
        //
        //----------------------------------------------------
      
        /// <summary>
        /// get the table of the daemons events history.
        /// </summary>
        /// <returns></returns>
        public DataTable getDaemonsList()
        {
            return daemonsDataTable;
        }
        //
        //----------------------------------------------------
        
        /// <summary>
        /// scan the daemons events history, and check if the switch booted correctly (taking into consideration the local role and switch type).
        /// </summary>
        /// <param name="localRole">local role (master/slave)</param>
        public void scanDaemonsStatus(string localRole, SWProfile switchProfile, SWFamily swFamily)
        {
            ArrayList launchedDaemons = new ArrayList();
            ArrayList mandatoryDaemons = new ArrayList(); 
            ArrayList mandatoryDaemonsFullName = new ArrayList();
            // list of the mandatory daemons and their full names
            //
            // For SX switches
            //
            if (swFamily == SWFamily.SX)
            {
                mandatoryDaemons = new ArrayList() { "tc", "pgm", "health", "restd", "ufma", "fw_updt", "portd", "topo", "hwd", "snmpd", "mibd", "ibd0", "smm", "sched" };
                mandatoryDaemonsFullName = new ArrayList() { "Temperature Control Daemon", "Process Group Daemon", "Health Daemon", "restd Daemon", "UFM Agent", "Process Group Daemon", "Port Manager", "Topology Manager", "HW Daemon", "SNMP Agent Daemon", "mibd Daemon", "IB Manager", "HA Daemon", "Scheduler Daemon" };
            }
            //--
            // For EFM FIT switches
            //
            if (swFamily == SWFamily.FIT)
            {
                mandatoryDaemons = new ArrayList() { "tc", 
                    "health", 
                    "hwd", 
                    "snmpd", 
                    "smm", 
                    "sched",
                    "crond",
                    "httpd",
                    "mgmtd",
                    "statsd",
                    "clusterd",
                    "sshd",
                    "wsmd"
                };
                mandatoryDaemonsFullName = new ArrayList() { "Temperature Control Daemon", 
                    "Health Daemon", 
                    "HW Daemon", 
                    "SNMP Agent Daemon",
                    "HA Daemon",
                    "Scheduler Daemon",
                    "Cron Daemon",
                    "HTTP Server Daemon",
                    "Management Daemon",
                    "Statistics Daemon",
                    "Clustering Daemon",
                    "SSH Daemon",
                    "Web Session Manager Daemon"
                };
            }
            //--

            //
            // ETH switches doesn't have: ibd0, smm
            //
            if (switchProfile == SWProfile.ETH || (switchProfile == SWProfile.NA && Methods.GetSwitchProfile(OUTPUT_DIR_PATH).Equals("Eth")))
            {
                // Eth switch
                // remove non-mandatory daemons for Eth switches from the list
                try
                {
                    mandatoryDaemons.Remove("ibd0"); mandatoryDaemonsFullName.Remove("IB Manager");
                    mandatoryDaemons.Remove("smm"); mandatoryDaemonsFullName.Remove("HA Daemon");
                }
                catch { }
            }

            //
            // switches with the role 'slave' doesn't have the following ...
            //
            if (localRole != null)
            {
                if (localRole.Equals("slave"))
                {
                    // remove non-mandatory daemons for management running as slave from the list
                    try
                    {
                        mandatoryDaemons.Remove("tc"); mandatoryDaemonsFullName.Remove("Temperature Control Daemon");
                        mandatoryDaemons.Remove("health"); mandatoryDaemonsFullName.Remove("Health Daemon");
                        mandatoryDaemons.Remove("restd"); mandatoryDaemonsFullName.Remove("restd Daemon");
                        mandatoryDaemons.Remove("ufma"); mandatoryDaemonsFullName.Remove("UFM Agent");
                        mandatoryDaemons.Remove("snmpd"); mandatoryDaemonsFullName.Remove("SNMP Agent Daemon");
                        mandatoryDaemons.Remove("mibd"); mandatoryDaemonsFullName.Remove("mibd Daemon");
                    }
                    catch { }
                }
            }
            //
            //
            string res = "* The following daemons were launched:\r\n";
            string missingMandatoryDaemons = "";
            daemonsLoadVerification = "* The switch booted correctly; all mandatory daemons were launched.\r\nMore details can be found under the 'Daemons' tab.";
            try
            {
                // scan the launched daemons, until the last restart 
                for (int i = 0; i < daemonsDataTable.Rows.Count; i++)
                {
                    string msg = daemonsDataTable.Rows[i]["Message"].ToString();
                    if (msg.Contains("Linux version"))
                    {
                        break;
                    }
                    if (msg.Contains("Launched") && msg.Contains("with pid")) //SX
                    {
                        string daemon = msg.Substring(msg.IndexOf("Launched") + "Launched ".Length);
                        launchedDaemons.Add(Regex.Split(daemon, " ")[0]);
                        res += "\t" + daemon + "\r\n";
                    }
                    if (msg.Contains("launched") && !msg.Contains("with pid")) //FIT
                    {
                        string daemon = Methods.RemoveExtraSpaces(msg);
                        daemon = Regex.Split(daemon, " ")[1];
                        daemon = daemon.Substring(0, daemon.IndexOf('['));
                        launchedDaemons.Add(daemon);
                        res += "\t" + daemon + "\r\n";
                    }
                }
                //
                for (int i = 0; i < mandatoryDaemons.Count; i++)
                {
                    if (!launchedDaemons.Contains(mandatoryDaemons[i]))
                    {
                        missingMandatoryDaemons += "\t" + mandatoryDaemons[i] + " (" + mandatoryDaemonsFullName[i] + ")" + "\r\n";
                    }
                }
                //
                if (!missingMandatoryDaemons.Equals(""))
                {
                    missingMandatoryDaemons = "* The following mandatory daemons were NOT launched:\r\n" + missingMandatoryDaemons;
                    daemonsLoadVerification = "* ERROR: The switch didn't boot correctly; part of the mandatory daemons were NOT launched.\r\nMore details can be found under the 'Daemons' tab.";
                    res = "ERROR: The switch didn't boot correctly.\r\n\r\n" + missingMandatoryDaemons + "\r\n" + res;
                }
                else
                {
                    res = "The switch booted correctly.\r\n\r\n" + res;
                }
            }
            catch { }
            daemonsStatus = res;
        }
        //
        //----------------------------------------------------
        
        /// <summary>
        /// implementation of dispose function for the IDisposable interface.
        /// </summary>
        public void Dispose()
        {
            if (daemonsDataTable != null)
            {
                daemonsDataTable.Dispose();
            }
            if (logsPrintGaps != null)
            {
                logsPrintGaps.Dispose();
            }

            if (ledIndicationsTable != null)
            {
                ledIndicationsTable.Dispose();
            }
        }
        //
        //----------------------------------------------------
        
        /// <summary>
        /// gets the daemons status summary (witch daemons were/were not launched).
        /// </summary>
        /// <returns></returns>
        public string getDaemonsSummary()
        {
            return daemonsStatus;
        }
        //
        //----------------------------------------------------
        
        /// <summary>
        /// get a small message regarding the status of the daemons
        /// </summary>
        /// <returns></returns>
        public string getDaemonsLoadVerification()
        {
            return daemonsLoadVerification;
        }
        //
        //----------------------------------------------------
        //
    }
}
